//
//  Transfers.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/01/26.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "Transfers.h"
#import "Peers.h"
#import "Shares.h"
#import "SolarSeekEngine.h"

@interface Transfer (Protected)
- (void)setActive:(BOOL)active;
@end

@implementation Transfer
- (id)init {
	[NSException raise:@"NotDesignatedInitializer" format:@"Please call the correct initializer."];
	[self release];
	return nil;
}

- (id)initWithPeer:(Peer *)peer token:(unsigned)token share:(Share *)share{
	if(self = [super init]) {
		_fileHandle = nil;
		_token = token;
		_peerForTransfer = [peer retain];
		_dateStarted = [[NSDate alloc] init];
		_active = NO;
		_secondsTransferred = 0.5;
		_share = [share retain];
		_state = TransferStateNone;
		_descriptionState = [[NSString alloc] initWithString:@"Unspecified."];
		_locationQueue = 0;
	}
	return self;
}

- (void)dealloc {
	[_fileHandle release];
	[_peerForTransfer release];
	[_dateStarted release];
	[_dateActiveLast release];
	[_descriptionState release];
	[_share release];
	[super dealloc];
}

- (id)initWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		_active = NO;
		_descriptionState = [[NSString alloc] initWithString:@"Unspecified."];
		_fileHandle = nil;
		_dateStarted = nil;
		_dateActiveLast = nil;
		_secondsTransferred = 0;
		if([coder containsValueForKey:@"state"])
			_state = [coder decodeIntForKey:@"state"];
		_state = TransferStateNone;
		_tokenPeerChosen = [coder decodeInt32ForKey:@"tokenPeerChosen"];
		_locationQueue = 0;
		_token = [[NSApp engine] tokenNext];

		NSString *peername = [coder decodeObjectForKey:@"peerName"];
		_peerForTransfer = [[[NSApp engine] peerNamed:peername] retain];
		_share = [[coder decodeObjectForKey:@"share"] retain];

		return self;
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}


- (void)encodeWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		if(_state==TransferStateFinished)
			[coder encodeInt:_state forKey:@"state"];
		[coder encodeObject:[[self peer] name] forKey:@"peerName"];
		[coder encodeObject:[self share] forKey:@"share"];
		[coder encodeInt32:_tokenPeerChosen forKey:@"tokenPeerChosen"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}



- (BOOL)isEqual:(id)other {
	return [other isKindOfClass:[self class]] && [[self share] isEqual:[other share]];
}

- (unsigned)hash {
	return [[self share] hash];
}

- (NSComparisonResult)compare:(Transfer *)other { return [[self share] compare:[other share]]; }

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
	if([key isEqual:@"state"]) return NO;
	else return [NSObject automaticallyNotifiesObserversForKey:key];
}

+ (void)initialize {
	NSArray *a = [NSArray arrayWithObject:@"countBytesTransferred"];
	[self setKeys:a triggerChangeNotificationsForDependentKey:@"countBytesRemaining"];
	[self setKeys:a triggerChangeNotificationsForDependentKey:@"dateFinished"];
	[self setKeys:a triggerChangeNotificationsForDependentKey:@"speed"];
}
	

#pragma mark KVO methods.		

- (unsigned long long)countBytesTotal { return [_share size]; }

- (unsigned long long)countBytesRemaining { 
	unsigned long long to = [self countBytesTotal];
	unsigned long long tr = [self countBytesTransferred];
	return to < tr ? 0 : to - tr;
}

- (NSDate *)dateFinished { return [[NSDate date] addTimeInterval:[self timeIntervalRemaining]]; }
- (NSDate *)dateStarted { return _dateStarted; }
- (NSString *)descriptionState { return _descriptionState; }
- (unsigned)locationQueue { return _locationQueue; }
- (Peer *)peer { return _peerForTransfer; }
- (double)speed { return ((double)[self countBytesTransferred])/[self timeIntervalSpentTransferring]; }
- (Share *)share { return _share; }
- (TransferState)state { return _state; }
- (unsigned)token { return _token; }
- (unsigned)tokenPeerChosen { return _tokenPeerChosen; }
- (BOOL)transferring { return _active; }

- (NSTimeInterval) timeIntervalSpentTransferring { 
	if(_active) {
		NSDate *d = [NSDate date];
		_secondsTransferred += [d timeIntervalSinceDate:_dateActiveLast];
		[_dateActiveLast release];
		_dateActiveLast = [d retain];
	}
	return _secondsTransferred+0.00001;	//avoid any divide by zeros
}

- (NSTimeInterval) timeIntervalRemaining {
	double rem = [self countBytesRemaining];
	double speed = [self speed];
	return rem/speed;
}

#pragma mark KVC methods


- (void)setActive:(BOOL)active {
	[self willChangeValueForKey:@"transferring"];
	if(active && _active==NO) {
		[_dateActiveLast release];
		_dateActiveLast = [[NSDate alloc] init];
	}
	_active = active;
	[self didChangeValueForKey:@"transferring"];
}

- (void)setLocationQueue:(unsigned)location {
	NSAssert([self state]==TransferStateIdle,@"Cannot set Queue location if we are not idle.");
	_locationQueue = location;
}

- (void)setState:(TransferState)state {
	switch(state) {
	case TransferStateFinished:
		[self setState:state description:@"Complete"];
		break;
	case TransferStateIdle:
		[self setState:state description:@""];
		break;
	case TransferStateFailed:
		[self setState:state description:@"Error"];
		break;
	case TransferStateStarting:
		[self setState:state description:@"Starting"];
		break;
	case TransferStateTransferring:
		[self setState:state description:@"Transferring"];
		break;
	case TransferStateNone:
	default:
		[NSException raise:@"BadValueException" format:@"Please choose a different state. %d is invalid",state];
		break;
	}
	[self setActive:state == TransferStateTransferring];
}

- (void)setState:(TransferState)state description:(NSString *)message {
	[self willChangeValueForKey:@"state"];
	[self willChangeValueForKey:@"descriptionState"];
	[_descriptionState release];
	_descriptionState = [message copy];
	_state = state;
	[self setActive:state==TransferStateTransferring];
	[self didChangeValueForKey:@"descriptionState"];
	[self didChangeValueForKey:@"state"];
}

- (void)setTokenPeerChosen:(unsigned)token { _tokenPeerChosen = token; }

#pragma mark actions

- (void)closeBuffer { [_fileHandle release]; _fileHandle = nil;}

@end

#pragma mark -

@implementation TransferDownload

- (id)initWithPeer:(Peer *)peer token:(unsigned)token share:(Share *)share {
	return [self init];
}

- (id)initWithPeer:(Peer *)peer token:(unsigned)token share:(SharePeer *)share 
			filenameTemporary:(NSString *)ft filenameFinished:(NSString *)ff {
	if(self = [super initWithPeer:peer token:token share:share]) {
		_filenameTemporary = [ft copy];
		_filenameFinished = [ff copy];
		_fm = [NSFileManager defaultManager];
	}	
	return self;
}

- (void)dealloc {
	[_filenameTemporary release];
	[_filenameFinished release];
	[super dealloc];
}

- (id)initWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		if(self = [super initWithCoder:coder]) {
			_filenameFinished = [[coder decodeObjectForKey:@"filenameFinished"] retain];
			_filenameTemporary = [[coder decodeObjectForKey:@"filenameTemporary"] retain];
			_fm = [NSFileManager defaultManager];
			return self;
		}
		else return nil;
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}


- (void)encodeWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		[super encodeWithCoder:coder];
		[coder encodeObject:_filenameFinished forKey:@"filenameFinished"];
		[coder encodeObject:_filenameTemporary forKey:@"filenameTemporary"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}


#pragma mark KVO/C

- (TransferType)type { return TransferTypeDownload; }
- (unsigned long long)countBytesTransferred { 
	if(_fileHandle != nil) 
		return [_fileHandle offsetInFile];
	else if([[NSFileManager defaultManager] fileExistsAtPath:[[self filenameCurrent] stringByStandardizingPath]]) {
		return [[[NSFileManager defaultManager] fileAttributesAtPath:[[self filenameCurrent] stringByStandardizingPath] traverseLink:YES] fileSize];
	}
	else return 0;
}

- (NSString *)filenameBuffer { return _filenameTemporary; }
- (NSString *)filenameCurrent { return _state == TransferStateFinished?[self filenameFinished]:[self filenameBuffer]; }
- (NSString *)filenameFinished { return _filenameFinished; }

#pragma mark actions

- (BOOL)touchDirectoriesForPath:(NSString *)file {
	if([[NSFileManager defaultManager] fileExistsAtPath:file] == NO 
		  && [file isEqual:@"/"]==NO
		  && [self touchDirectoriesForPath:[file stringByDeletingLastPathComponent]] == YES)
		return [[NSFileManager defaultManager] createDirectoryAtPath:file attributes:nil];
	else
		return [file isEqual:@"/"]==NO;
}

- (BOOL)touchFile:(NSString *)file {
	if([[NSFileManager defaultManager] fileExistsAtPath:file]==NO
			&& [self touchDirectoriesForPath:[file stringByDeletingLastPathComponent]]) {
		return [[NSFileManager defaultManager] createFileAtPath:file contents:nil attributes:nil];
	}
	else
		return [[NSFileManager defaultManager] fileExistsAtPath:file];
}

- (BOOL)openBuffer {
	NSAssert(_fileHandle==nil,@"The buffer is already open.");
	NSAssert([self state] != TransferStateFinished,@"This transfer has already finished.");
	
	NSString *fn = [[self filenameBuffer] stringByStandardizingPath];
	if([self touchFile:fn]) {
		_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:fn] retain];
		if(_fileHandle)
			[_fileHandle seekToEndOfFile];
	}
		
	return _fileHandle!=nil;
}

- (void)writeData:(NSData *)data {
	//fejta: TODO: medium: this should be buffered.
	NSAssert(_fileHandle != nil,@"The buffer must be open.");
	[self willChangeValueForKey:@"countBytesTransferred"];
	[_fileHandle writeData:data];
	[self didChangeValueForKey:@"countBytesTransferred"];
}
@end

#pragma mark -

@implementation TransferUpload 

- (id)initWithPeer:(Peer *)peer token:(unsigned)token share:(Share *)share {
	if(self = [super initWithPeer:peer token:token share:share]) {
		_transferred = 0;
	}
	return self;
}

- (unsigned long long)countBytesTransferred { 
	return _fileHandle==nil?_transferred:[_fileHandle offsetInFile]; 
}

- (NSString *)filenameCurrent { return [(ShareLocal *)_share filenameLocal]; }
- (NSString *)filenameBuffer { return [(ShareLocal *)_share filenameLocal]; }
- (TransferType)type { return TransferTypeUpload; }
- (BOOL)openBuffer {
	NSAssert([self state] != TransferStateFinished,@"This transfer has already finished.");
	[self closeBuffer];
	
	_fileHandle = [[NSFileHandle fileHandleForReadingAtPath:[self filenameBuffer]] retain];
	return _fileHandle != nil;
}

- (BOOL)seekToOffset:(unsigned long long)count {
	NSAssert(_fileHandle != nil,@"The buffer must be open to skip bytes.");
	[_fileHandle seekToFileOffset:count];
	return count == [_fileHandle offsetInFile];
}

- (NSData *)readData { return [_fileHandle availableData]; }
- (NSData *)readDataOfLength:(unsigned)length { return [_fileHandle readDataOfLength:length]; }

- (void)closeBuffer {
	_transferred = [self countBytesTransferred];
	[super closeBuffer];
}
@end